package com.tmanager.persistence.tx;

import java.util.Deque;
import java.util.LinkedList;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.FlushModeType;


import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;

@Singleton
public class EntityManagerProvider implements Provider<EntityManager>, UserTransaction {

    @Inject
    private final Provider<EntityManagerFactory> emf;
    private final ThreadLocal<Deque<EntityManagerWrapper>> local = new ThreadLocal<Deque<EntityManagerWrapper>>() {
        @Override
        protected Deque<EntityManagerWrapper> initialValue() {
            return new LinkedList<EntityManagerWrapper>();
        }
    };

    @Inject
    protected EntityManagerProvider(Provider<EntityManagerFactory> emf) {
        this.emf = emf;
    }

    /**
     * Return active EntityManagerWrapper associated with current thread or null.
     * This method is limited for internal usage only.
     *
     * @return EntityManagerWrapper or null
     */
    protected EntityManagerWrapper getCurrent0() {
        return local.get().peekLast();
    }

    /**
     * Return active EntityManager associated with current thread or null
     *
     * @return EntityManager or null
     */
    public EntityManager getCurrent() {
        EntityManagerWrapper wrapper = getCurrent0();
        if (wrapper == null) {
            throw new RuntimeException("out of transaction scope");
        }
        return wrapper;
    }

    protected EntityManagerWrapper createNew() {
        EntityManager entityManager = emf.get().createEntityManager();
        EntityManagerWrapper em = new EntityManagerWrapper(entityManager);
        local.get().addLast(em);
        return em;
    }

    protected EntityManagerWrapper release(EntityManagerWrapper emw) {
        EntityManagerWrapper current = local.get().removeLast();
        if (emw != current) {
            throw new RuntimeException("EntityManager is not matched with current");
        }
        return current;
    }

    @Override
    public EntityManager get() {
        return getCurrent();
    }

    @Override
    public void begin() {
        assert getCurrent0() == null;
        EntityManager em = createNew().getOriginal();
        em.setFlushMode(FlushModeType.COMMIT);
        em.getTransaction().begin();
    }

    @Override
    public void commit() {
        try {
            getCurrent0().getOriginal().getTransaction().commit();
        } finally {
            local.get().removeLast();
        }
    }

    @Override
    public void rollback() {
        try {
            getCurrent0().getOriginal().getTransaction().rollback();
        } finally {
            local.get().removeLast();
        }
    }

    @Override
    public void setRollbackOnly() {
        getCurrent0().getOriginal().getTransaction().setRollbackOnly();
    }

    @Override
    public boolean isRollbackOnly() {
        return getCurrent0().getOriginal().getTransaction().getRollbackOnly();
    }

    @Override
    public boolean isActive() {
        EntityManagerWrapper emw = getCurrent0();
        return emw != null && emw.getOriginal().getTransaction().isActive();
    }
}
